﻿//#define SKIN_DEBUG

using LogSystem;
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using Yoozoo.Core.Extensions.Engine;

namespace Yoozoo.Gameplay.RTS
{
    public interface GPUSkinning_IAnimationEventReceiver
    {
        void SendAniEventMessage(string functionName, int param);
    }

    public class GPUSkinning_SpawnObject
    {

        public int SpawnObjectId;
        private UEInstanceAnimation _ueInstanceAnimation;

        struct AnimatorParam
        {
            public int paramHash;
            public bool paramValue;
        }

        public bool transformChange;
        
        
        public float timeScale = 1.0f;
        AnimatorParam[] m_parameters = null;

        private bool[] eventTriggers = new bool[16];


        public GPUSkinningAnimatorState m_CurrentState = null;
        public GPUSkinningAnimatorState m_NextState = null;

        //public GPUSkinning_BoneAnimation m_boneAnimation = null;
        //private GPUSkinning_BoneAnimation m_boneAnimationFusion = null;

        public GPUSkinning_Animation m_Animation = null;
        private GPUSkinning_Animation m_animationFusion = null;


        private Transform m_transform = null;
        public ModelInstaning model = null;
        public GPUSkinning_IAnimationEventReceiver eventReceiver;
        public int instancingId = -1;

        public float frameStartIndex = 0;

        public float frameEndIndex = 0;

        public float currentTime = 0;

        public float m_CurrentTimeFusion = 0;

        public float m_FrameStartIndexFusion = 0;

        public float m_FrameEndIndexFusion = 0;

        public float m_FusionAnimTime = 0;

        public float m_Progress = 0;
        
        private bool isFusion = false;

        public GPUSkinning_AllAnimation allAnimation;
        public GPUSkinning_AllBoneAnimation allBoneAnimation;
        public Transform[] mountPoints;

        public int totalFrameCount = 0;

        public float normalizedTime;
        public float frameIndex;

        public float m_FrameIndexFusion;
        
        private int m_FrameIndexFusionLast = -1;

        public bool useFrameLerp = true;

        public string AnimationName
        {
            get { return m_Animation != null ? m_Animation.animName : string.Empty; }
        }

        public string CurStateName
        {
            get { return m_CurrentState != null ? m_CurrentState.m_Name : string.Empty; }
        }

        public string NextStateName
        {
            get { return m_NextState != null ? m_NextState.m_Name : string.Empty; }
        }

        public bool IsInTrasition
        {
            get { return m_NextState != null; }
        }

        //private GPUSkinningBoneAnimationTransition m_transition = null;
        private GPUSkinningAnimatorTransition m_transition = null;

        public Dictionary<int, int> m_TransitionHashCodeIndexMap = null;

        public Dictionary<int, int> m_StateNameHashCodeIndexMap = null;
        List<int> cacheChangeTiggerList = new List<int>();
        public Dictionary<GPUSkinningAnimatorState, Dictionary<int, List<GPUSkinningAnimatorTransition>>> m_ParamToTransition;
        private int changeTiggerCount = 0;
        private float frameCount;
        public int[] paramIndexToHashCode;
        public Dictionary<string, int> paramNameToHash;
        public  Matrix4x4 cacheLocalToWorldMatrix;

        private void PlayAnimationInternal(GPUSkinningAnimatorState state,float startTime = 0)
        {
            m_CurrentState = state;
            if (state.m_MotionIndex < allAnimation.animations.Length)
            {
                currentTime = startTime;
                normalizedTime = 0;
                if (state.m_MotionIndex >= allAnimation.animations.Length )
                {
                    Debug.LogError("state.m_MotionIndex:" + state.m_MotionIndex);
                }

                if (state.m_MotionIndex < 0)
                {
                    return;
                }
                m_Animation = allAnimation.animations[state.m_MotionIndex];
                for (int i = 0; i < eventTriggers.Length; i++)
                    eventTriggers[i] = false;
                
                int frameStartIndex = m_Animation.frameStartIndex;

                this.frameStartIndex = (float) (frameStartIndex + 0.5) / totalFrameCount;
                frameEndIndex = (float) (frameStartIndex + m_Animation.frameCount - 1) / totalFrameCount;
                frameCount = frameEndIndex - this.frameStartIndex;
            }
#if UNITY_EDITOR && SKIN_DEBUG
            else
            {
                Debug.LogError("State not found animation: " + state.m_Name);
            }
#endif
        }

#if UNITY_EDITOR && SKIN_DEBUG
        const int max_List = 20;
        public List<KeyValuePair<string, string>> triggerQueue = new List<KeyValuePair<string, string>>();
        public List<string> tranStateQueue = new List<string>();
        public List<string> conditionQueue = new List<string>();
       
#endif

        /// <summary>
        /// 动画是否执行
        /// </summary>
        public bool AnimatorEnabled { get; private set; }

        /// <summary>
        /// 渲染是否执行
        /// </summary>
        public bool RendererEnabled { get; private set; }

        private void PlayFusionAnimationInternal(GPUSkinningAnimatorState nextState,
            GPUSkinningAnimatorTransition targetTransition = null)
        {
            if (nextState.m_MotionIndex < allAnimation.animations.Length && nextState.m_MotionIndex >= 0)
            {
                isFusion = true;
                m_NextState = nextState;
                m_Progress = 0;
                m_CurrentTimeFusion = 0;

                m_transition = targetTransition;
                
                m_FrameIndexFusionLast = math.clamp((int)(normalizedTime * (m_Animation.frameCount - 1)), 0, m_Animation.frameCount - 1);
                m_animationFusion = allAnimation.animations[nextState.m_MotionIndex];

                float targetDuration = m_animationFusion.length;
                float duration = m_transition.m_FixedDuration
                    ? m_transition.m_Duration
                    : m_transition.m_Duration * m_Animation.length;

                int frameStartIndex = m_animationFusion.frameStartIndex +
                                      Mathf.CeilToInt(m_transition.m_Offset * targetDuration * m_Animation.fps);
                m_FrameStartIndexFusion = (float) (frameStartIndex) / totalFrameCount;
                m_FrameEndIndexFusion = (float) (frameStartIndex + Mathf.CeilToInt(duration * m_Animation.fps)) /
                                        totalFrameCount;
                //BattleDebug.LogError("Fusion " + m_FrameIndexFusion + " " + m_Progress + " " + m_FrameStartIndexFusion + " " + m_FrameEndIndexFusion+ " " + frameStartIndex);
            }
#if UNITY_EDITOR && SKIN_DEBUG
            else
            {
                Debug.LogError("NextState not found animation: " + state.m_Name);
            }

            if (tranStateQueue.Count >= max_List)
                tranStateQueue.RemoveAt(0);
            tranStateQueue.Insert(tranStateQueue.Count, state.m_Name);
#endif
        }

        public void InjectUEInstanceAnimation(UEInstanceAnimation animation)
        {
            _ueInstanceAnimation = animation;
        }

    public void Initial(Transform transform, ModelInstaning model)
    {
            m_transform = transform;

            // 创建挂点
            int mountPointsLength = allBoneAnimation.mountPoints.Length;
            mountPoints = new Transform[mountPointsLength];
            model.InitMountPoints(allBoneAnimation.mountPoints, ref mountPoints);

            int idx = 0;
            m_parameters = new AnimatorParam[allBoneAnimation.parameters.Length];
            foreach (var triggerHashCode in allBoneAnimation.parameters)
            {
                m_parameters[idx].paramHash = triggerHashCode.hashCode;
                m_parameters[idx++].paramValue = false;
            }
            
            PlayAnimation(allBoneAnimation.defaultStateName);
        }

    public bool SetTrigger(int hashCode, bool isReplay = false, float speed = 1)
    {
        cacheChangeTiggerList.Add(hashCode);
        changeTiggerCount++;
        int idx = 0;
        bool have = false;

        for (; idx < m_parameters.Length; idx++)
        {
            if (m_parameters[idx].paramHash == hashCode)
            {
                m_parameters[idx].paramValue = true;
                have = true;
                break;
            }
        }

        if (!have)
            return false;

#if UNITY_EDITOR && SKIN_DEBUG
            if (triggerQueue.Count >= max_List)
                triggerQueue.RemoveAt(0);

            string codeStack = string.Empty;
            StackTrace st = new StackTrace(true);
            for (int i = 1; i < st.FrameCount; i++)
            {
                // Note that high up the call stack, there is only
                // one stack frame.
                StackFrame sf = st.GetFrame(i);
                codeStack +=
 sf.GetMethod().ToString() + "->: " + sf.GetFileName() + ": " + sf.GetFileLineNumber() + "@";
            }

            string stateName =
 idx < allBoneAnimation.parameters.Length ? allBoneAnimation.parameters[idx].name : "Error Code";
            triggerQueue.Insert(triggerQueue.Count, new KeyValuePair<string, string>(stateName, codeStack));
#endif
        this.isReplay = isReplay;
        if (isReplay)
        {
            m_NextState = null;
        }

        timeScale = speed;
        return true;
    }

    private bool isReplay = false;

    public void SetTrigger(string triggerName,bool isReplay = false)
    {
#if UNITY_EDITOR && ENABLE_CITY_AI_PROFILER
        Debug.LogFormat(LogModule.CityAI, "[SKINNING]SetTrigger: {0}, {1}, current state = {2}", triggerName, m_transform.name, m_CurrentState.m_Name);
#endif
        bool success = false;
        if (!string.IsNullOrEmpty(triggerName))
        {
            success = SetTrigger(triggerName.GetHashCode(),isReplay);
        }
#if UNITY_EDITOR   
        if (!success)
            Debug.LogWarningFormat(LogModule.City, "[SKINNING]没有找到Trigger: {0}, {1}", triggerName, m_transform.name);
#endif
    }

    public void ResetTrigger()
        {
            for(int idx = 0; idx < m_parameters.Length; idx++)
            {
                m_parameters[idx].paramValue = false;
            }
            cacheChangeTiggerList.Clear();
            changeTiggerCount = 0;
        }

        public void CheckTrigger()
        {

            if (m_NextState != null || changeTiggerCount == 0 )
            {
                return;
            }
            if(m_CurrentState == null)
            {
                return;
            }
            GPUSkinningAnimatorTransition targetTransition = null;
            
            var paramToTransition = m_ParamToTransition[m_CurrentState];
            for (int i = 0; i < changeTiggerCount; i++)
            {
                List<GPUSkinningAnimatorTransition> transitionList;
                if(paramToTransition.TryGetValue(cacheChangeTiggerList[i],out transitionList))
                {
                    int tc = transitionList.Count;
                    for (int j = 0; j < tc; j++)
                    {
                        var mc = transitionList[j].m_ConditionHashes;
                        bool result = true;
                        for (int k = 0; k < mc.Length; k++)
                        {
                            if (!m_parameters[mc[k]].paramValue)
                            {
                                result = false;
                                break;
                            }
                        }

                        if (result)
                        {
                            targetTransition = transitionList[j];
                            break;
                        }
                    }
                }

                if (targetTransition!=null) break;

            }

            if (targetTransition == null || targetTransition == m_transition && !isReplay)
            {
                return;
            }

#if UNITY_EDITOR && SKIN_DEBUG
            string nextStateName = allBoneAnimation.m_AllStates[targetTransition.m_TargetStateIndex].m_Name;
            string tranName = (CurStateName + "=>" + nextStateName);
            if (!m_TransitionHashCodeIndexMap.ContainsKey(tranName.GetHashCode()))
            {
                Debug.LogError("not found tran: "+ tranName);
                return;
            }
#endif
            //m_transition = allBoneAnimation.m_Transitions[m_TransitionHashCodeIndexMap[tranName.GetHashCode()]];

            var nextState = allBoneAnimation.m_AllStates[targetTransition.m_TargetStateIndex];
            PlayFusionAnimationInternal(nextState, targetTransition);

            //PlayAnimationInternal(nextState);

            cacheChangeTiggerList.Clear();
            changeTiggerCount = 0;
        }

        public void PlayAnimation(int stateNameHashCode, bool forceFusionAtOnce = false)
        {
            int stateIndex = 0;
            if (m_StateNameHashCodeIndexMap.TryGetValue(stateNameHashCode, out stateIndex))
            {
                PlayAnimationInternal(allBoneAnimation.m_AllStates[stateIndex]);
            }

#if UNITY_EDITOR && SKIN_DEBUG
            else
            {
                Debug.LogError(" not found State HashCode: " + stateNameHashCode);
            }
#endif
        }

        public void PlayAnimation(string stateName, bool forceFusionAtOnce = false)
        {
            int stateNameHashCode = stateName.GetHashCode();

            if (m_StateNameHashCodeIndexMap.ContainsKey(stateNameHashCode))
                PlayAnimationInternal(allBoneAnimation.m_AllStates[m_StateNameHashCodeIndexMap[stateNameHashCode]]);
#if UNITY_EDITOR && SKIN_DEBUG
            else
            {
                Debug.LogError(" not found State: " + stateName);
            }
#endif
        }

        public float UpdateFusion(float deltaTime)
        {
            if (m_NextState == null)
            {
                return -1;
            }

           
            float dtts = (deltaTime * timeScale);
            m_CurrentTimeFusion += dtts;
 
            

            float duration = m_transition.m_FixedDuration 
                ? m_transition.m_Duration
                : m_transition.m_Duration * m_Animation.length;

            m_Progress = duration < float.Epsilon ? 1.0f : m_CurrentTimeFusion / duration;
            m_FrameIndexFusion = duration < float.Epsilon ? m_FrameStartIndexFusion : m_FrameStartIndexFusion + (m_CurrentTimeFusion / duration - math.floor(m_CurrentTimeFusion / duration)) * (m_FrameEndIndexFusion - m_FrameStartIndexFusion);
            //BattleDebug.LogError(m_FrameIndexFusion + " " + m_Progress + " " + m_FrameStartIndexFusion + " " + m_FrameEndIndexFusion);
            if (m_Progress >= 1)
            {
                PlayAnimationInternal(m_NextState);
                m_NextState = null;
                float length = m_Animation.length;
                currentTime = duration < float.Epsilon ? 0:(m_transition.m_Offset * length) + m_Progress * duration - dtts;
                normalizedTime = currentTime / length - math.floor(currentTime / length);
                frameIndex = frameStartIndex + normalizedTime * frameCount;
                isFusion = false;
                m_Progress = 0;
                return -1;
            }
            return m_FrameIndexFusion;
        }

        public int frameFusionOffset = 0;
        private void UpdateCurrentFrameLerp()
        {
            var frameTime = (1f / totalFrameCount);
            var currentFrameIndex = Mathf.Floor(frameIndex / frameTime);
            m_FrameStartIndexFusion = currentFrameIndex / totalFrameCount;
            var nextFrameIndex = currentFrameIndex + 1;
            if (nextFrameIndex >= m_Animation.frameStartIndex + m_Animation.frameCount - 1)
            {
                if (m_CurrentState.m_IsLooping)
                {
                    frameFusionOffset = -((int)currentFrameIndex - m_Animation.frameStartIndex);
                    //nextFrameIndex = m_Animation.frameStartIndex;
                }
                else
                {
                    frameFusionOffset = 0;
                    m_Progress = 0;
                    m_FrameIndexFusion = frameIndex;
                    return;
                }
            }
            else
            {
                frameFusionOffset = 1;
            }
            m_FrameEndIndexFusion = nextFrameIndex / totalFrameCount;
            var offset = m_FrameEndIndexFusion - frameIndex;
            m_Progress = 1 - offset / frameTime;
            m_FrameIndexFusion = frameIndex;
            //BattleDebug.LogError(m_Progress + " " + offset + " " + normalizedTime  + " " + frameIndex + " " + m_FrameEndIndexFusion + " " + currentFrameIndex + " " + nextFrameIndex + " " + frameFusionOffset);
        }

        public Color RimColor;
        public float FresnelExp;
        public float RimIntensity;
        public float Alpha = 1;
        public Vector4 MaskColor { get; set; }
        public float InRoom;
        public float BreathLight = 0;
        public float DissolveAmount = 0;
        private static float defaultShadowAlpha = 180f / 255f;
        public Vector4 ShadowColor = new Vector4(0,0,0,defaultShadowAlpha);
        public float ShadowHeight = 0f;

        public void SetShadowHeight(float shadowHeight)
        {
            ShadowHeight = shadowHeight;
        }
        
        public void SetShadowAlpha(float alpha)
        {
            ShadowColor.w = alpha * defaultShadowAlpha;
        }
        
        public void SetDissolveAmount(float dissolveAmount)
        {
            DissolveAmount = dissolveAmount;
        }
        
        public void SetRim(int instancingId,Color rimColor, float fresnelExp, float rimIntensity)
        {
            RimColor = rimColor;
            FresnelExp = fresnelExp;
            RimIntensity = rimIntensity;
        }
        public void SetAlpha(float alpha)
        {
            Alpha = alpha;
        }

        public void SetCampColor(Color color)
        {
            MaskColor = color;
        }

        public void SetInRoom(float inRoom)
        {
            InRoom = inRoom;
        }

        public void SetBreathLight(float breathLight)
        {
            BreathLight = breathLight;
        }


        public void SetAnimatorEnabled(bool value)
        {
            if (AnimatorEnabled != value)
            {
                AnimatorEnabled = value;
                _ueInstanceAnimation.SetAnimatorEnabled(value);
            }
        }

        public void SetRendererEnabled(bool value)
        {
            if (RendererEnabled != value)
            {
                RendererEnabled = value;
                _ueInstanceAnimation.SetRendererEnabled(value);
            }
        }

        public float Update(float deltaTime)
        {
            if (m_Animation == null)
            {
                UnityEngine.Debug.LogErrorFormat("m_boneAnimation == null");
                return 0f;
            }

            if (!AnimatorEnabled)
            {
                return frameIndex;
            }

            UpdateFusion(deltaTime);

            currentTime += (deltaTime * timeScale);
            float previousIndex = frameIndex;

            float ti = math.floor(currentTime / m_Animation.length);
            normalizedTime = currentTime / m_Animation.length - ti;
            frameIndex = frameStartIndex + normalizedTime * frameCount;
            //BattleDebug.LogError(frameIndex + " " + normalizedTime + " " + currentTime);
            if (changeTiggerCount > 0)
            {
                CheckTrigger();
            }

            if (frameIndex < previousIndex && previousIndex >= frameStartIndex && previousIndex <= frameEndIndex &&
                frameIndex >= frameStartIndex && frameIndex <= frameEndIndex)
            {
                if (m_CurrentState.m_IsLooping)
                {
                    // 循环
                    for (int i = 0; i < eventTriggers.Length; i++)
                        eventTriggers[i] = false;
                }
                else
                {
                    normalizedTime = 1.0f;
                    frameIndex = frameEndIndex;

                    // 检查跳转
                    if (m_NextState == null)
                    {
                        // 这里可能已经因为Trigger而要跳转，不需要检查后续的跳转了
                        foreach (var transition in m_CurrentState.transitions)
                        {
                            if (!transition.HasCondition)
                            {
                                // 真正的跳转要提前判断，并且进行融合，这里直接跳转到下一个状态了
                                var nextState = allBoneAnimation.m_AllStates[transition.m_TargetStateIndex];
                                Debug.LogFormat(LogModule.CityAI, "[SKINNING]state {0} -> {1} on {2}",
                                    m_CurrentState.m_Name, nextState.m_Name, m_transform.name);
                                PlayAnimationInternal(nextState);
                                break;
                            }
                        }
                    }
                }
            }

            int len = mountPoints.Length;
            int frameIndexInAnimation = math.clamp((int)(normalizedTime * (m_Animation.frameCount - 1)), 0, m_Animation.frameCount - 1);
            var fusionMountIndex = -1;
            var fusionMountIndexCurrent = m_FrameIndexFusionLast;
            GPUSkinning_Animation next = m_animationFusion;
            if (isFusion)
            {
                float duration = m_transition.m_FixedDuration 
                    ? m_transition.m_Duration
                    : m_transition.m_Duration * m_Animation.length;
                fusionMountIndex = Mathf.CeilToInt(duration * (m_animationFusion.frameCount - 1));
                //BattleDebug.LogError("fusion " + fusionMountIndex + " " + m_Progress + " " + m_CurrentTimeFusion);
            }
            else
            {
                if (useFrameLerp)
                {
                    UpdateCurrentFrameLerp();
                    fusionMountIndexCurrent = (int)(frameIndex * totalFrameCount) - m_Animation.frameStartIndex;
                    fusionMountIndex = fusionMountIndexCurrent + frameFusionOffset;
                    next = m_Animation;
                }
            }
            //BattleDebug.LogError(frameIndexInAnimation + " " + fusionMountIndex);
            for (int i = 0; i < len; i++)
            {
                var mp = mountPoints[i];
                try
                {
                    if (fusionMountIndex >= 0)
                    {
                        //BattleDebug.LogError(fusionMountIndex + " " + m_FrameIndexFusion);
                        var lastPosition = m_Animation.mountPoints[i].positions[fusionMountIndexCurrent];
                        var nextPosition = next.mountPoints[i].positions[fusionMountIndex];
                        mp.localPosition = Vector3.Lerp(lastPosition,nextPosition,m_Progress);
                        var lastAngle = m_Animation.mountPoints[i].rotations[fusionMountIndexCurrent];
                        var nextAngle = next.mountPoints[i].rotations[fusionMountIndex];
                        var lastQuaternion = Quaternion.Euler(lastAngle);
                        var nextQuaternion = Quaternion.Euler(nextAngle);
                        //BattleDebug.LogError(lastAngle + " " + nextAngle);
                        mp.localRotation = Quaternion.Lerp(lastQuaternion, nextQuaternion, m_Progress);
                    }
                    else
                    {
                        mp.localPosition = m_Animation.mountPoints[i].positions[frameIndexInAnimation];
                        mp.localEulerAngles = m_Animation.mountPoints[i].rotations[frameIndexInAnimation];
                    }
                }
                catch (Exception e)
                {
                    Debug.LogErrorFormat(LogModule.CitySimulation,
                        "SetMountPointFailed: {0} {1} {2} frameIndex = {3}{5}\n{4}", m_transform.name, CurStateName,
                        currentTime, frameIndexInAnimation, e,i);
                }
            }

            // 处理事件
            float animationTime = normalizedTime * m_Animation.length;
            for (var i = 0; i < m_Animation.events.Length; i++)
            {
                var animationEvent = m_Animation.events[i];
                if (!eventTriggers[i] && animationEvent.time < animationTime)
                {
                    // 'NpcModel' AnimationEvent 'HideBubble' on animation 'a_incity_male_sitsofa_lietosit' has no receiver! Are you missing a component?

#if UNITY_EDITOR && ENABLE_CITY_AI_PROFILER
                    Debug.LogFormat(LogModule.CityAI,
                        "[SKINNING]'{0}' AnimationEvent {1} on animation '{2}', time = {3}",
                        model.name, animationEvent.functionName, m_Animation.animName, animationEvent.time);
#endif
                    eventTriggers[i] = true;
                    //model.SendMessage(animationEvent.functionName);
                    if (eventReceiver != null)
                    {
                        eventReceiver.SendAniEventMessage(animationEvent.functionName, animationEvent.intParameter);
                    }
                }
            }

            if (transformChange)
            {
                cacheLocalToWorldMatrix = m_transform.localToWorldMatrix;
                transformChange = false;
            }

            return frameIndex;
        }

        public void checkTransition()
        {

        }

        internal void SetSpeed(float speed)
        {
            timeScale = speed;
        }
    }
}
